package com.gitee.wsl.flow

//package com.dylanc.longan

import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.Channel.Factory.UNLIMITED
import kotlinx.coroutines.channels.ProducerScope
import kotlinx.coroutines.flow.*
import timber.log.Timber
import kotlin.experimental.ExperimentalTypeInference
import kotlin.time.Duration


object F{
    @OptIn(ExperimentalTypeInference::class)
    fun <T> flow(@BuilderInference block: suspend FlowCollector<T>.() -> Unit): Flow<T> = kotlinx.coroutines.flow.flow(block)

    fun <T> flowOf(vararg elements: T): Flow<T> = kotlinx.coroutines.flow.flowOf(*elements)

    /**
     * Creates a flow that produces the given [value].
     */
    fun <T> flowOf(value: T): Flow<T> = kotlinx.coroutines.flow.flowOf(value)

    @OptIn(ExperimentalTypeInference::class)
    fun <T> channelFlow(@BuilderInference block: suspend ProducerScope<T>.() -> Unit): Flow<T> = kotlinx.coroutines.flow.channelFlow(block)

    @OptIn(ExperimentalTypeInference::class)
    fun <T> callbackFlow(@BuilderInference block: suspend ProducerScope<T>.() -> Unit): Flow<T> = kotlinx.coroutines.flow.callbackFlow(block)

    fun <T> flowFromSuspend(function: suspend () -> T): Flow<T> =
        kotlinx.coroutines.flow.flow { emit(function()) }


//@Suppress("UNCHECKED_CAST", "LongParameterList")
//inline fun <T1, T2, R> combine(
//    flow: Flow<T1>,
//    flow2: Flow<T2>,
//    crossinline transform: suspend (T1, T2) -> R
//): Flow<R> = kotlinx.coroutines.flow.combine(
//    flow,
//    flow2
//) { args: Array<*> ->
//    transform(
//        args[0] as T1,
//        args[1] as T2
//    )
//}

    @Suppress("UNCHECKED_CAST", "LongParameterList")
    inline fun <T1, T2, T3, R> combine(
        flow: Flow<T1>,
        flow2: Flow<T2>,
        flow3: Flow<T3>,
        crossinline transform: suspend (T1, T2, T3) -> R
    ): Flow<R> = kotlinx.coroutines.flow.combine(
        flow,
        flow2,
        flow3,
    ) { args: Array<*> ->
        transform(
            args[0] as T1,
            args[1] as T2,
            args[2] as T3,
        )
    }

    @Suppress("UNCHECKED_CAST", "LongParameterList")
    inline fun <T1, T2, T3, T4, T5, R> combine(
        flow: Flow<T1>,
        flow2: Flow<T2>,
        flow3: Flow<T3>,
        flow4: Flow<T4>,
        flow5: Flow<T5>,
        crossinline transform: suspend (T1, T2, T3, T4, T5) -> R
    ): Flow<R> = kotlinx.coroutines.flow.combine(
        flow,
        flow2,
        flow3,
        flow4,
        flow5,
    ) { args: Array<*> ->
        transform(
            args[0] as T1,
            args[1] as T2,
            args[2] as T3,
            args[3] as T4,
            args[4] as T5
        )
    }

    @Suppress("UNCHECKED_CAST", "LongParameterList")
    inline fun <T1, T2, T3, T4, T5, T6, R> combine(
        flow: Flow<T1>,
        flow2: Flow<T2>,
        flow3: Flow<T3>,
        flow4: Flow<T4>,
        flow5: Flow<T5>,
        flow6: Flow<T6>,
        crossinline transform: suspend (T1, T2, T3, T4, T5, T6) -> R
    ): Flow<R> = kotlinx.coroutines.flow.combine(
        flow,
        flow2,
        flow3,
        flow4,
        flow5,
        flow6
    ) { args: Array<*> ->
        transform(
            args[0] as T1,
            args[1] as T2,
            args[2] as T3,
            args[3] as T4,
            args[4] as T5,
            args[5] as T6
        )
    }

    @Suppress("UNCHECKED_CAST", "LongParameterList")
    inline fun <T1, T2, T3, T4, T5, T6, T7, R> combine(
        flow: Flow<T1>,
        flow2: Flow<T2>,
        flow3: Flow<T3>,
        flow4: Flow<T4>,
        flow5: Flow<T5>,
        flow6: Flow<T6>,
        flow7: Flow<T7>,
        crossinline transform: suspend (T1, T2, T3, T4, T5, T6, T7) -> R
    ): Flow<R> = kotlinx.coroutines.flow.combine(
        flow,
        flow2,
        flow3,
        flow4,
        flow5,
        flow6,
        flow7
    ) { args: Array<*> ->
        transform(
            args[0] as T1,
            args[1] as T2,
            args[2] as T3,
            args[3] as T4,
            args[4] as T5,
            args[5] as T6,
            args[6] as T7
        )
    }

    @Suppress("UNCHECKED_CAST", "LongParameterList")
    inline fun <T1, T2, T3, T4, T5, T6, T7, T8, R> combine(
        flow: Flow<T1>,
        flow2: Flow<T2>,
        flow3: Flow<T3>,
        flow4: Flow<T4>,
        flow5: Flow<T5>,
        flow6: Flow<T6>,
        flow7: Flow<T7>,
        flow8: Flow<T8>,
        crossinline transform: suspend (T1, T2, T3, T4, T5, T6, T7, T8) -> R
    ): Flow<R> = kotlinx.coroutines.flow.combine(
        flow,
        flow2,
        flow3,
        flow4,
        flow5,
        flow6,
        flow7,
        flow8
    ) { args: Array<*> ->
        transform(
            args[0] as T1,
            args[1] as T2,
            args[2] as T3,
            args[3] as T4,
            args[4] as T5,
            args[5] as T6,
            args[6] as T7,
            args[7] as T8
        )
    }

    @Suppress("UNCHECKED_CAST", "LongParameterList")
    inline fun <T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, R> combine(
        flow: Flow<T1>,
        flow2: Flow<T2>,
        flow3: Flow<T3>,
        flow4: Flow<T4>,
        flow5: Flow<T5>,
        flow6: Flow<T6>,
        flow7: Flow<T7>,
        flow8: Flow<T8>,
        flow9: Flow<T9>,
        flow10: Flow<T10>,
        crossinline transform: suspend (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10) -> R
    ): Flow<R> = kotlinx.coroutines.flow.combine(
        flow, flow2, flow3, flow4, flow5, flow6, flow7, flow8, flow9, flow10
    ) { args: Array<*> ->
        transform(
            args[0] as T1,
            args[1] as T2,
            args[2] as T3,
            args[3] as T4,
            args[4] as T5,
            args[5] as T6,
            args[6] as T7,
            args[7] as T8,
            args[8] as T9,
            args[9] as T10
        )
    }

    @Suppress("UNCHECKED_CAST", "LongParameterList")
    inline fun <T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, R> combine(
        flow: Flow<T1>,
        flow2: Flow<T2>,
        flow3: Flow<T3>,
        flow4: Flow<T4>,
        flow5: Flow<T5>,
        flow6: Flow<T6>,
        flow7: Flow<T7>,
        flow8: Flow<T8>,
        flow9: Flow<T9>,
        flow10: Flow<T10>,
        flow11: Flow<T11>,
        crossinline transform: suspend (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11) -> R
    ): Flow<R> = kotlinx.coroutines.flow.combine(
        flow, flow2, flow3, flow4, flow5, flow6, flow7, flow8, flow9, flow10, flow11
    ) { args: Array<*> ->
        transform(
            args[0] as T1,
            args[1] as T2,
            args[2] as T3,
            args[3] as T4,
            args[4] as T5,
            args[5] as T6,
            args[6] as T7,
            args[7] as T8,
            args[8] as T9,
            args[9] as T10,
            args[10] as T11
        )
    }

}



/**
 * This function is used in every use-case except for Paging related use-cases.
 * It returns a flow of type [Stage] which is a sealed class containing [Stage.Success] and
 * [Stage.Exception] object. [doWork] is where we perform fetching and caching of the data.
 *
 * Please refer to our documentation for more [details](https://kasem-sm.github.io/SlimeKT/guide/interactors/#default-interactor)
 */
/*
inline fun <T,R> CoroutineDispatcher.task(
    crossinline doWork: suspend () -> T,
): Flow<com.gitee.wsl.base.Result<T,R>> {
    return flow {
        try {
            withTimeout(TimeUnit.MINUTES.toMillis(5)) {
                emit(com.gitee.wsl.base.Loading(Nothing))
                val data = doWork.invoke()
                emit(com.gitee.wsl.base.Ok(data))
            }
        } catch (e: TimeoutCancellationException) {
            emit(com.gitee.wsl.base.Err(e))
        }
    }.catch { throwable ->
        emit(com.gitee.wsl.base.Err(throwable))
    }.flowOn(this)
}
*/





/*
 @OptIn(ExperimentalTypeInference::class)
 inline fun <T, R> Flow<T>.unsafeTransform(
    @BuilderInference crossinline transform: suspend FlowCollector<R>.(value: T) -> Unit
): Flow<R> = flow {
    collect { value ->
        return@collect transform(value)
    }
}*/


/**
 * Create a stateful flow, with the initial value of null, but never emits a null value.
 * Helper method to create a new flow without suspending and without initial value
 * The flow collector will just wait for the first value
 */
fun <T : Any> Flow<T>.shareLatest(
    scope: CoroutineScope,
    started: SharingStarted = SharingStarted.WhileSubscribed(replayExpirationMillis = 0),
    tag: String? = null,
) = this
    .onStart { if (tag != null) Timber.tag(tag).d( "shareLatest(...) start")}
    .onEach { if (tag != null) Timber.tag(tag).d( "shareLatest(...) emission: $it")}
    .onCompletion { if (tag != null) Timber.tag(tag).d( "shareLatest(...) completed.")}
    .catch {
        if (tag != null) Timber.tag(tag).d( "shareLatest(...) catch(): $it")
        throw it
    }
    .stateIn(
        scope = scope,
        started = started,
        initialValue = null
    )
    .filterNotNull()

fun <T : Any?> Flow<T>.replayingShare(scope: CoroutineScope) = this.shareIn(
    scope = scope,
    replay = 1,
    started = SharingStarted.WhileSubscribed(replayExpiration = Duration.ZERO)
)


fun <T> Flow<T>.onError(block: suspend (Throwable) -> Unit) = this.catch {
    block(it)
    throw it
}



fun <T> Flow<T>.setupCommonEventHandlers(tag: String, identifier: () -> String) = this
    .onStart { Timber.tag(tag).d( "${identifier()}.onStart()")}
    .onEach { Timber.tag(tag).d( "${identifier()}.onEach(): $it")}
    .onCompletion { Timber.tag(tag).d( "${identifier()}.onCompletion()")}
    .catch {
        if (it is CancellationException) {
            Timber.tag(tag).d( "${identifier()} cancelled" )
        } else {
            Timber.tag(tag).e( "${identifier()} failed: ${it}" )
            throw it
        }
    }


/**
 * Projects each source value to a [Flow] which is merged in the output [Flow] only if the previous projected [Flow] has completed.
 * If value is received while there is some projected [Flow] sequence being merged, it will simply be ignored.
 *
 * This method is a shortcut for `map(transform).flattenFirst()`. See [flattenFirst].
 *
 * ### Operator fusion
 *
 * Applications of [flowOn], [buffer], and [produceIn] _after_ this operator are fused with
 * its concurrent merging so that only one properly configured channel is used for execution of merging logic.
 *
 * @param transform A transform function to apply to value that was observed while no Flow is executing in parallel.
 */
/*
fun <T, R> Flow<T>.flatMapFirst(transform: suspend (value: T) -> Flow<R>): Flow<R> =
    map(transform).flattenFirst()
*/

/**
 * Converts a higher-order [Flow] into a first-order [Flow] by dropping inner [Flow] while the previous inner [Flow] has not yet completed.
 */
/*fun <T> Flow<Flow<T>>.flattenFirst(): Flow<T> = channelFlow {
    val busy = atomic(false)

    collect { inner ->
        if (busy.compareAndSet(false, true)) {
            // Do not pay for dispatch here, it's never necessary
            launch(start = CoroutineStart.UNDISPATCHED) {
                try {
                    inner.collect { send(it) }
                    busy.value = false
                } catch (e: CancellationException) {
                    busy.value = false
                }
            }
        }
    }
}*/




suspend fun <T> Flow<T>.isEmpty(): Boolean = take(1).count() == 0
suspend fun <T> Flow<T>.isNotEmpty(): Boolean = !isEmpty()

suspend inline operator fun <T> FlowCollector<T>.invoke(value: T) = emit(value)

fun <T> Flow<T>.takeNotNull() = mapNotNull { it }
fun <T> Flow<T>.filterNotNull() = takeNotNull()

suspend fun <T> Flow<T?>.firstNotNull() = first { it != null }!!

